﻿using System;
using System.Collections.Generic;

namespace wielomiany {
	/// <summary>
	/// klasa wielomianu o współczynnikach zespolonych
	/// </summary>
	class Polynomial {
		#region pola
		private Complex[] factors; //współczynniki
		private byte[,] variables; //potęgi zmiennych
		private bool isProper; //przechowane informacji o poprawności utworzonego wialomianu
		#endregion
		#region konstruktory
		/// <summary>
		/// konstruktor domyślny, tworzy pusty wielomian rzutowany na wartość false
		/// </summary>
		public Polynomial () {
			this.factors = new Complex[0];
			this.variables = new byte[0, 0];
			this.isProper = false;
		}
		/// <summary>
		/// konstruktor wielomianu jednej zmiennej
		/// zawsze jest rzutowany do wartości true
		/// </summary>
		/// <param name="factors">tablica współczynników w kolejności rosnącej względem stopni kojenych czynników</param>
		public Polynomial (Complex[] factors) {
			this.factors = factors;
			this.variables = new byte[factors.Length, 1];
			for (byte i = 0;i < factors.Length;i++) {
				this.variables[i, 0] = i;
			}
			isProper = true;
		}
		/// <summary>
		/// konstruktor pozwalający utworzyć dowolny wielomian poprzez zdefiniowanie macierzy rzadkiej współczynników
		/// w przypadku niezgodnej długości tablicy współczynników i potęg, zwracany jest pusty wielomian rzutowany do wartości false
		/// </summary>
		/// <param name="factors">tablica współczynników zespolonych</param>
		/// <param name="variables">tablica dwuwymiarowa z potęgami</param>
		/// <example>new Polynomial(New Complex[] {new Complex(1, 1), new Complex(2,2)}, new byte[,]{{2,3}, {4,5}});
		///utworzy nowy wielomian w którym:
		///współczynnik 1+i zostanie przyporządkowany czynnikowi x_0^2x_1^3
		///współczynnik 2+2i zostanie przyporządkowany czynnikowi x_0^4x_1^5</example>
		public Polynomial (Complex[] factors, byte[,] variables) {
			if (factors.Length == variables.GetLength(0)) {
				this.factors = factors;
				this.variables = variables;
				isProper = true;
				this.simplify();
			} else {
				isProper = false;
			}
		}
		#endregion
		#region operatory arytmetyczne
		/// <summary>
		/// operator liczenia wartości w punkcie
		/// </summary>
		/// <param name="w">wielomian, dla któego liczona jest wartość</param>
		/// <param name="point">tablica z współrzędnymi punktu, w któym liczymy wartość</param>
		/// <returns>
		/// liczbę zespoloną reprezentującą wartość wielomianu w danym punkcie
		/// jeżeli ilość współrzędnych punktu jest różna od ilości zmiennych wielomianu zwracana jest liczba zespolona rzutowana do wartości false
		/// </returns>
		public static Complex operator | (Polynomial w, double[] point) {//
			Complex value = new Complex();
			int arg = 1;
			if (point.Length == w.variables.GetLength(1)) {//jeżeli jest podanych tyle współrzędnych punktu, ilu zmiennych jest wilomian, to możemy policzyć wartość...
				for (int i = 0;i < w.factors.Length;i++) {
					arg = 1;
					for (int k = 0;k < point.Length;k++) {
						arg *= (int) Math.Pow(point[k], w.variables[i, k]);
					}
					value += w.factors[i] * arg;
				}
			} // ... jeżeli nie, to zwracamy liczbę zespoloną "z błędem"
			return value;
		}
		/// <summary>
		/// operator dodawania dwóch wielomianów
		/// </summary>
		/// <param name="x">pierwszy wielomian</param>
		/// <param name="y">drugi wielomian</param>
		/// <returns>wielomian reprezentujący sumę podanych wielomianów</returns>
		public static Polynomial operator + (Polynomial x, Polynomial y) {
			List<Complex> factors = new List<Complex>();
			List<byte[]> variables = new List<byte[]>();
			List<int> added = new List<int>(); //lista zawierająca indeksy dodanych współczynników
			if (x.variables.GetLength(1) == y.variables.GetLength(1)) {
				for (int i = 0;i < x.factors.Length;i++) { //przechodzimy po wszystkich współczynnikach jednego wielomianu, sprawdzamy, czy taki jest w drugim wielomianie i dodajemy
					byte[] xIndexes = x.getIndexes(i);
					factors.Add(x.factors[i]);
					variables.Add(x.getIndexes(i));
					if (y.getIndex(xIndexes) > -1) {
						factors[i] += y.getFactor(xIndexes);
						added.Add(y.getIndex(xIndexes));
					}
				}
				for (int i = 0;i < y.factors.Length;i++) { //te współczynniki, które nie zostały dodane, daodajemy
					if (!added.Contains(i)) {
						factors.Add(y.factors[i]);
						variables.Add(y.getIndexes(i));
					}
				}
			} else {
				return new Polynomial(); //jeżeli ilości zmiennych dodawanych wielomianów się zgadzają, to dodajemy, w przeciwnym wypadku zwracamy wielomian "z błędem";
			}
			byte[,] variablesOut = new byte[variables.Count,x.variables.GetLength(1)]; //kopiujemy zawartość listy variables do tablicy dwuwymiarowej
			for(int i=0; i<variablesOut.GetLength(0); i++) {
				for(int k=0; k<variablesOut.GetLength(1); k++) {
					variablesOut[i,k] = variables[i][k];
				}
			}
			Polynomial output = new Polynomial(factors.ToArray(), variablesOut);//towrzymy, upraszczamy i zwracamy sumę wielomianów z argumentów
			output.simplify(); 
			return output;
		}
		/// <summary>
		/// operator odejmowania
		/// </summary>
		/// <param name="x">odjemna</param>
		/// <param name="y">odjemnik</param>
		/// <returns>wielomian stanowiący różnicę</returns>
		public static Polynomial operator - (Polynomial x, Polynomial y) {
			return x + ((-1) * y);
		}
		/// <summary>
		/// mnożenie wielomianu przez stałą
		/// </summary>
		/// <param name="x">wielomian</param>
		/// <param name="y">stała</param>
		/// <returns>zwraca nowy wielomian z wymnożonymi współczynnikami</returns>
		public static Polynomial operator * (Polynomial x, double y) {
			Polynomial z = new Polynomial((Complex[]) x.factors.Clone(), (byte [,])x.variables.Clone());
			for (int i = 0;i < z.factors.Length;i++) {
				z.factors[i] *= y;
			}
			return z;
		}
		/// <summary>
		/// mnożenie wielomianu przez stałą
		/// </summary>
		/// <param name="y">stała</param>
		/// <param name="x">wielomian</param>
		/// <returns>zwraca nowy wielomian z wymnożonymi współczynnikami</returns>
		public static Polynomial operator * (double y, Polynomial x) {
			return x * y;
		}
		/// <summary>
		/// mnożenie dwóch wielomianów
		/// </summary>
		/// <param name="x">pierwszy czynnik</param>
		/// <param name="y">drugi czynnik</param>
		/// <returns>nowy wielomian stanowiący iloczyn</returns>
		public static Polynomial operator * (Polynomial x, Polynomial y) {
			if (x.variables.GetLength(1) != y.variables.GetLength(1)) { //jeżeli liczby zmiennych się nie zgadzają, zwracamy wielomian "z błędem"
				return new Polynomial();
			}
			List<Complex> factors = new List<Complex>();
			List<byte[]> variables = new List<byte[]>();
			for (int i = 0;i < x.factors.Length;i++) { //mnożymy każdy z każdym i dodajemy wynik do list factors i variables
				for (int k = 0;k < y.factors.Length;k++) {
					factors.Add(new Complex(x.factors[i] * y.factors[k]));
					byte [] v = new byte[x.variables.GetLength(1)];
					for(int l=0; l<x.variables.GetLength(1); l++) {
						v[l] = (byte)(x.variables[i,l]+y.variables[k,l]);
					}
					variables.Add(v);
				} 
			}
			byte[,] output = new byte[factors.Count, x.variables.GetLength(1)];//zrucamy listę variables do tablicy dwuwymiarowej
			for (int i = 0;i < factors.Count;i++) {
				for (int k = 0;k < x.variables.GetLength(1);k++) {
					output[i, k] = variables[i][k];
				}
			}
			Polynomial outp =  new Polynomial(factors.ToArray(), output);//tworzymy, upraszczamy i zwracamy wielomian
			outp.simplify();
			return outp;
		}
		/// <summary>
		/// dzielenie wielomianu przez stałą
		/// </summary>
		/// <param name="x">wielomian</param>
		/// <param name="y">stała</param>
		/// <returns>wielomian ze współczynnikami podzielonymi przez stałą</returns>
		public static Polynomial operator / (Polynomial x, double y) {
			return x * (1 / y);
		}
		#endregion
		#region rzutowania i konwersje
		/// <summary>
		/// rzutowanie do stringa
		/// </summary>
		/// <param name="n">ilość miejsc po przecinku</param>
		/// <returns>string reprezentujący naturalny zapis wielomianu</returns>
		public string ToString (int n) {
			string output = "";
			for (int i = 0;i < this.factors.Length;i++) {
				output += "(" + this.factors[i].ToString(n) + ")";
				for (int k = 0;k < this.variables.GetLength(1);k++) {
					output += "x_" + k + "^" + this.variables[i, k];
				}
				if (i < this.factors.Length - 1) {
					output += "+";
				}
			}
			return output;
		}
		/// <summary>
		/// rzutowanie do stringa
		/// </summary>
		/// <returns>string reprezentujący naturalny zapis wielomianu z dokładnością dwóch miejsc po przecinku</returns>
		public override string ToString () {
			return this.ToString(2);
		}
		/// <summary>
		/// niejawne rzutowanie do zmiennej typu bool
		/// </summary>
		/// <param name="x">wielomian do rzutowania</param>
		/// <returns>zwraca wartość zmiennej określającej poprawność utworzonego wielomianu</returns>
		public static implicit operator bool (Polynomial x) {
			return x.isProper;
		} 
		#endregion
		#region inne potrzebne rzeczy
		/// <summary>
		/// zwraca współczynnik stojący przy podanych potęgach zmiennych
		/// </summary>
		/// <param name="ws">tablica potęg kolejnych zmiennych</param>
		/// <returns>współczynnik zespolony</returns>
		private Complex getFactor (byte[] ws) {
			Complex factor = new Complex();
			for (int i = 0;i < this.variables.GetLength(0);i++) {
				bool found = true;
				for (int k = 0;k < this.variables.GetLength(1);k++) {
					if (ws[k] != this.variables[i, k]) {
						found = false;
						break;
					}
				}
				if (found) {
					factor = new Complex(this.factors[i]);
				}
			}
			return factor;
		}
		/// <summary>
		/// zwraca potęgi stojące przy współczynniku o podanym indeksie
		/// </summary>
		/// <param name="i">indeks współczynnika w tablicy Factors</param>
		/// <returns></returns>
		private byte[] getIndexes (int i) {
			byte[] indexes = new byte[this.variables.GetLength(1)];
			for (int k = 0;k < this.variables.GetLength(1);k++) {
				indexes[k] = this.variables[i, k];
			}
			return indexes;
		}
		/// <summary>
		/// zwraca indeks podanych potęg w tablicy variables
		/// </summary>
		/// <param name="ws">tablica określająca potęgi</param>
		/// <returns></returns>
		private int getIndex (byte[] ws) {
			int index = -1;
			for (int i = 0;i < this.variables.GetLength(0);i++) {
				bool found = true;
				for (int k = 0;k < this.variables.GetLength(1);k++) {
					if (ws[k] != this.variables[i, k]) {
						found = false;
						break;
					}
				}
				if (found) {
					index = i;
				}
			}
			return index;
		}
		/// <summary>
		/// upraszcza zapis wielomianu
		/// </summary>
		private void simplify () {
			List<Complex> factors = new List<Complex>();
			List<byte[]> variables = new List<byte[]>();
			for (int i = 0;i < this.factors.GetLength(0);i++) {//kopiujemy wszystke współczynniki do list factors i variables
				Complex f = this.factors[i];
				factors.Add(f);
				byte[] v = new byte[this.variables.GetLength(1)];
				for (int k = 0;k < v.GetLength(0);k++) {
					v[k] = this.variables[i, k];
				}
				variables.Add(v);
			}
			for (int i = 0;i < factors.Count;i++) {//przechodzimy po wszystkich potęgach wielomianu i dla każdej szukamy takiej, która jest jeszcze raz zdefiniowana
				for (int k = i+1;k < factors.Count;k++) {
					byte[] vi = variables[i];
					byte[] vk = variables[k];
					bool theSame = true;
					for (int l = 0;l < vi.GetLength(0);l++) {
						if (vi[l] != vk[l]) {
							theSame = false;
							break;
						}
					}
					if (theSame) { //jeżeli znaleźliśmy dwie potęgi o takich samych współczynnikach, to dodajemy współczynniki i usuwamy je z list
						factors[i] += factors[k];
						//factors[k] = new Complex();
						factors.RemoveAt(k);
						variables.RemoveAt(k);
						k--;
					}
				}
			}
			byte[,] outputVariables = new byte[factors.Count, variables[0].GetLength(0)];//przerabiamy z powrotem listę variables na tablicę dwuwymiarową
			for (int i = 0;i < variables.Count;i++) {
				for (int k = 0;k < outputVariables.GetLength(1);k++) {
					outputVariables[i, k] = variables[i][k];
				}
			}
			this.variables = outputVariables;//podmieniamy współczynniki wielomianu na te wygenerowane przez metodę
			this.factors = factors.ToArray();
		}
		#endregion
	}
}